<?php
namespace Lavender;
use Forxx\Api\Utils;

/**
 * Class Session
 * @package Lavender
 * @property \Lavender\SessionStorage $storage
 */
class Session implements \ArrayAccess
{
	protected $key;
	protected $request_time;
	protected $timeout;
	protected $updated;
	protected $storage;

	protected $id = null;
	protected $secret;

	protected $data = array();
	protected $inited = false;
	protected $changed = false;

	public function __construct($key, $request_time, $timeout, $storage = null)
	{
		$this->key = $key;
		$this->request_time = $request_time;
		$this->timeout = $timeout;
        $this->storage = $storage;

        $this->init();
	}

    public function login($uid,$expired = null){
        $this->id = $uid;
        if(empty($expired)){
            $expired = $this->timeout;
        }
        SessionStorage::set_raw($this->key,$uid,$expired);
    }

	public function __destruct()
	{
        if($this->id)
        {
            $this->save_data();
        }
	}

    /**
     * @return mixed
     */
    public function getId()
    {
        if (!$this->inited) {
            $this->init();
        }
        return $this->id;
    }

	public function is_valid()
	{
        if (!$this->inited) {
            $this->init();
        }
		return empty($this->data) ? false : true;
	}

	/**
	 * destroy current session
	 *
	 * @return string
	 */
	public function destroy()
	{
        if ($this->id) {
            $this->data = array();
            SessionStorage::set_raw($this->key,0,1);//1秒后销毁
            $this->save_data();
        }
	}


	protected function init()
	{
		$this->inited = true;
		//check session id & key
		if (empty($this->key) ) {
			return null;
		}
        $hash = Core::get_config("const","session_hash_key");
        //token 格式不正确
        if(!Utils::checkTokenFormat($this->key,$hash)){
            return null;
        }
        try{
            $id = SessionStorage::get_raw($this->key);
            if (empty($id) ) {
                return  null;
                //throw new Exception\Auth('session id not found on server.', Errno::SESSION_INVALID);
            }
            $this->id = $id;
            $this->data = $this->read_data();
            if (empty($this->data) ) {
                throw new Exception\Auth('session record not found on server.', Errno::SESSION_INVALID);
            }
        }
        catch(Exception $e){
            Core::log("session_error",date("Y-m-d H:i:s ").$e->getMessage()."\n");
        }
		return $this->id;
	}

	protected function read_data()
	{
		if (!$this->id) {
			return array();
		}
        return $this->get_handle()->get_data();
	}

	protected function save_data()
	{
		if (! $this->id) {
			return true;
		}

		//destory
		if (empty($this->data) ) {
			return $this->get_handle()->destroy();
		}

		//update modify time only
		if (!$this->changed) {
			//update session time interval: 5min
		    return $this->get_handle()->update_data([], $this->timeout);
		}
		//update
		return$this->get_handle()->save_data($this->data, $this->timeout);
	}

    /**
     * return \Lavender\SessionStorage
     * @throws Exception
     * @throws Exception\Auth
     */
    protected function get_handle()
    {
        if (!$this->inited) {
            $this->init();
        }
        if(!$this->id)
        {
            return ;
        }
        $storageName = $this->storage;
        if(empty($storageName))
        {
            $storageName =   '\Lavender\SessionStorage';
        }
        if(!class_exists($storageName))
        {
            throw new Exception("class {$storageName} not exists !");
        }
        $handle = new $storageName($this->key);
        if(!$handle instanceof SessionStorage)
        {
            throw new Exception("session storage {$storageName} invalid");
        }
        return $handle;
    }


	public function offsetSet($offset, $value)
	{
        if (!$this->inited) {
            $this->init();
        }
		if ($offset === null) {
			throw new Exception('key is empty.', Errno::PARAM_INVALID);
		}
		$this->changed = true;
		$this->data[$offset] = $value;
        $this->save_data();
	}

	public function offsetUnset($offset)
	{
        if (!$this->inited) {
            $this->init();
        }
		$this->changed = true;
		unset($this->data[$offset]);
        $this->save_data();
	}

	public function offsetExists($offset)
	{
        if (!$this->inited) {
            $this->init();
        }
		return isset($this->data[$offset]);
	}

	public function offsetGet($offset)
	{
        if (!$this->inited) {
            $this->init();
        }
		return isset($this->data[$offset]) ? $this->data[$offset] : null;
	}
}
